<?php
/**
* Factory class
* 
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* 
* @copyright Copyright (c) 2008-2009 Ne8, LLC <josh.ribakoff@gmail.com>
* @license New BSD License
* @version 0.1
* @category Shuffler
* @package Shuffler_Factory
*/
class Shuffler_Factory
{
	static $_instance; 
    
    /**
     * Singleton instance
     *
     * @return Shuffler_Factory
     */
    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }
    
    /**
    * Creates a null object for a given identifier, defaults to 0
    * 
    * @param optional integer for id
    * @return an instance of Metator_Model that implements Shuffler_Null
    * @throws Exception
    */
    static public function createNullModel( $modelName, $id = 0 )
    {
        $nullName = $modelName . '_Null';
        if( !class_exists( $nullName ) )
        {
            // code generation
            $string = 'class ' . $nullName . ' extends ' . $modelName . ' implements Shuffler_Null { }';
            eval( $string );
            if( !class_exists( $nullName ) )
            {
                throw new Exception( 'Code generation failed' );
            }
        }
        
        $reflectionClass = new ReflectionClass( $nullName );
        
        if( !class_exists( $nullName ) )
        {
            throw new Exception( $nullName . ' null model doesnt exist' ); 
        }
        
        class_exists( $nullName );
        
        $reflectionMethod = new ReflectionMethod ( $nullName, '__construct' );
        $argCount = $reflectionMethod->getNumberOfRequiredParameters();
        $params = $reflectionMethod->getParameters();
        $args = array( $id );
        for( $i = 1; $i < $argCount; $i++ )
        {
            $val = null;
            if( $c = $params[ $i ]->getClass() )
            {
                $val = self::createNullModel( $c->getName() );
            }
            array_push( $args, $val );
        }
        $null = $reflectionClass->newInstanceArgs( $args );
        if( !( $null instanceof Shuffler_Null ) )
        {
            throw new Exception( $nullName . ' must implement Shuffler_Null' ); 
        }
        return $null;
    }
    
    /**
    * Get list of fields that don't have mappings
    * 
    * @return a collection of fields that haven't been mapped yet
    */
    static public function getFieldsNotMapped( $mapper )
    {
        $return = array();
        foreach( $mapper->getFields() as $field )
        {
            if( !$mapper->hasMappingFor( $field ) )
            {
                array_push( $return, $field );
            }
        }
        return new ArrayIterator( $return );
    }
    
    /**
    * @var array of all mappers
    */
    protected $mappers = array();  
    
    static public function getReadAdapter()
    {
        $adapter = 'ZendDb';
        $adapter = 'Shuffler_Mapper_Adapter_' . $adapter;
        $adapter = new $adapter();
        return $adapter;
    }
    
    /**
    * @return Shuffler_Mapper_Adapter_ZendDb
    */
    static public function getWriteAdapter()
    {
        $adapter = 'ZendDb';
        $adapter = 'Shuffler_Mapper_Adapter_' . $adapter;
        $adapter = new $adapter();
        return $adapter;
    }
    
    public function getMapper( $modelName, $adapter = null )
    {
        if( empty( $modelName ) )
        {
            throw new Exception( 'model name must not be blank' );
        }
        if( !class_exists( $modelName, true ) )
        {
            throw new Exception( $modelName . ' must refer to a valid [existing] class' );
        }
        if( is_null( $adapter ) )
        {
            $adapter = self::getReadAdapter();
        }
        if( isset( $this->mappers[ $modelName ] ) && !( $this->mappers[ $modelName ] instanceof Shuffler_Mapper_Null ) )
        {
            return  $this->mappers[ $modelName ];
        }
        
        $this->mappers[ $modelName ] = new Shuffler_Mapper_Null();
        $class = $modelName . '_Mapper';
        if( file_exists( MODULE_PATH . '/' . str_replace( '_', '/', $class ) . '.php' ) )
        {
            $mapper = new $class( $modelName, $adapter );
        }
        else
        {
            $mapper = new Shuffler_Mapper( $modelName, $adapter );    
        } 
        $this->mappers[ $modelName ] = $mapper;
        if( !$mapper->hasMappings() )
        {
            self::setUpMappings( $mapper );
        }
        return $mapper;
    }
    
    public function getMappers()
    {
        return $this->mappers;
    }
    
    public function clearIdentityMaps()
    {
        foreach( $this->getMappers() as $mapper )
        {
            $mapper->clearIdentityMap();
        }
    }
    
    /**
    * auto-detect mappings
    */
    static function setUpMappings( &$mapper )
    {
        self::addFieldMapping( $mapper, 'id' );
        foreach( self::getFieldsNotMapped( $mapper ) as $field )
        {
            self::addFieldMapping( $mapper, $field );
        }
    }
    
    /**
    * Adds a mapping for a field, automatically detects single valued foreign keys / vs embedded fields
    * 
    * @param string name of field
    */
    static function addFieldMapping( &$mapper, $field )
    {
        if( $field == 'id' )
        {
            // if it is ID it is the primary key, make it immutable.
            $mapper->addPrimaryKey( $field );
        }
        else if( substr( $field, -3 ) == '_id' ) 
        {
            // if it ends in _id its probably a foreign key
            $class = str_replace( '_', ' ', $field );
            $class = trim( ucwords( strtolower( $class ) ) );
            $class = str_replace( ' ', '_', $class );
            $class = substr( $class, 0, strlen( $class ) - 3);
            $mapper->addSingle( $class, array( 'field' => $field ) );
        }
        else
        {
            // attempt to add it
            $mapper->addMapping( $field ); 
        }
    }
    
 }